package legoserver;


import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;

/**
 * defines special data type for the grid
 */
public class AStarGridCell extends java.awt.Component implements Serializable {
  public static final int SET_BLOCKS = 0, SET_START = 1, SET_FINISH = 2;
  public static final double NORMAL = 1, EASY = 0.3, TOUGH = 5;
  public static final double VERY_TOUGH = 10, BLOCK = Double.MAX_VALUE;
  private static double newBlockStrength = BLOCK;
  private static int editMode = SET_START;
  private static AStarGridCell startCell;
  private static AStarGridCell finishCell;
  private boolean isStart = false;
  public boolean isFinish = false;
  public static boolean tidy = false;
  private static Vector cells = new Vector();
  private static boolean showPath = true;
  private static boolean showDist = true;
  private double cost = 1.0;
  private transient boolean used = false;
  private transient double distFromStart = -1;
  private transient double distFromFinish = -1;
  private boolean partOfPath = false;
  private Point position;

  /**
   * constructor sets tidy true and enables event of the cell
   */
  public AStarGridCell() {
    cells.addElement(this);
    tidy = true;
    enableEvents(AWTEvent.MOUSE_EVENT_MASK);
  }

  /**
   * constructor with option for making this cell impassable
   * @param block boolean, set to true if this cell can not be passed through
   */
  public AStarGridCell(boolean block) {
    this();
    setTotalBlock(block);
  }

  /**
   * sets position to point p
   * @param p position point
   */
  public void setPosition(Point p) {
    position = p;
  }

  /**
   * gets position of the cell
   * @return point position
   */
  public Point getPosition() {
    return position;
  }

  /**
   * sets edit mode for the blocks
   * @param mode int value SET_BLOCKS = 0, SET_START = 1, SET_FINISH = 2
   */
  public static void setEditMode(int mode) {
    editMode = mode;
    // System.out.println("mode set to " + mode);
  }

  /**
   * mouse event listener which switches edit mode
   * @param e mouse event
   */
  public void processMouseEvent(MouseEvent e) {
    super.processMouseEvent(e);
    if (e.getID() == e.MOUSE_CLICKED) {
      setShowPath(false);
      switch(editMode) {
        case(SET_BLOCKS):
          if (cost != newBlockStrength) { cost = newBlockStrength; }
          else { cost = NORMAL; }
          repaint();
          break;
        case(SET_START):
          setStart(true);
          break;
        case(SET_FINISH):
          setFinish(true);
          break;
      }
    }
   //
  }

  /**
   * add cost from start
   * @param distSoFar double value
   */
  public void addToPathFromStart(double distSoFar) {
    used = true;
    if (distFromStart == -1) {
      distFromStart = distSoFar + cost;
      return;
    }
    if (distSoFar + cost < distFromStart) { distFromStart = distSoFar + cost; }
  }

  /**
   * add cost from finish
   * @param distSoFar double value
   */
  public void addToPathFromFinish(double distSoFar) {
    used = true;
    if (distFromFinish == -1) {
      distFromFinish = distSoFar + cost;
      return;
    }
    if (distSoFar + cost < distFromFinish) { distFromFinish = distSoFar + cost; }
  }

  /**
   * get cost
   * @return cost
   */
  public double getCost() {
    return cost;
  }

  /**
   * set cost
   * @param c double value
   */
  public void setCost(double c) {
    cost = c;
  }

  /**
   * gets the start cell
   * @return start cell
   */
  public static AStarGridCell getStartCell() {
    return startCell;
  }

  /**
   * is start set
   * @return boolean
   */
  public boolean isStart() {
    return startCell == this;
  }

  /**
   * set or remove start cell
   * @param flag is set
   */
  public void setStart(boolean flag) {
    if (flag) {
      AStarGridCell temp = this;
      if (startCell != null) {
        temp = startCell;
        temp.setStart(false);
      }
      startCell = this;
      isStart = true;
      repaint();
      temp.repaint();
    }
    else { isStart = false; }
  }

  /**
   * gets finish cell
   * @return finish cell
   */
  public static AStarGridCell getFinishCell() {
    return finishCell;
  }

  /**
   * is finish set
   * @return boolean is set
   */
  public boolean isFinish() {
    return finishCell == this;
  }

  /**
   * sets or remove finish cell
   * @param flag is finish set
   */
  public void setFinish(boolean flag) {
  //     System.out.println(this.position.);
    if (flag) {
      AStarGridCell temp = this;
      if (finishCell != null) {
        temp = finishCell;
        temp.setFinish(false);
      }
      finishCell = this;
      isFinish = true;
      repaint();
      temp.repaint();
      
    }
    else { isFinish = false; }
  }

  /**
   * is it a total block
   * @return boolean
   */
  public boolean isTotalBlock() {
    return cost == BLOCK;
  }

  /**
   * set or remove a total block
   * @param flag boolean is set
   */
  public void setTotalBlock(boolean flag) {
    if (flag) { cost = BLOCK; }
    else { cost = NORMAL; }
  }

  /**
   * is the cell used
   * @return boolean is used
   */
  public boolean isUsed() {
    return used;
  }

  /**
   * resets the cell
   */
  private void resetCell() {
    used = false;
    setPartOfPath(false);
    distFromStart = distFromFinish = -1;
  }

  /**
   * resets all cells
   */
  public static void reset() {
    for (int i = 0; i < cells.size(); i++) {
      ((AStarGridCell) cells.elementAt(i)).resetCell();
    }
  }

  /**
   * sets cost to normal
   */
  private void clearCell() {
    setCost(NORMAL);
  }

  /**
   * sets cost of all cell to normal
   */
  public static void clearAll() {
    for (int i = 0; i < cells.size(); i++) {
      ((AStarGridCell) cells.elementAt(i)).clearCell();
    }
  }

  /**
   * sets block with strength
   * @param s double cost value
   */
  public static void setNewBlockStrength(double s) {
    if (s < 0) { newBlockStrength = BLOCK; }
    else { newBlockStrength = s; }
  }

  /**
   * sets show path flag
   * @param flag boolean is shown
   */
  public static void setShowPath(boolean flag) {
    showPath = flag;
  }

  /**
   * is show path set
   * @return boolean is shown
   */
  public static boolean isShowPath() {
    return showPath;
  }

  /**
   * is cell a part of the path
   * @return boolean
   */
  public boolean isPartOfPath() {
    return partOfPath;
  }

  /**
   * sets cell as a part of the path
   * @param flag boolean
   */
  public void setPartOfPath(boolean flag) {
    partOfPath = flag;
  }

  /**
   * get the distance from the start cell
   * @return distance from start
   */
  public double getDistFromStart() {
    if (AStarGridCell.startCell == this) { return 0; }
    if (isTotalBlock()) { return -1; }
    return distFromStart;
  }

  /**
   * overrides paint method for coloring the grid cells
   * @param g graphic object
   */
  public void paint(Graphics g) {
    Dimension size = getSize();
    g.setColor(Color.white);
    if (cost != NORMAL) {
      if (cost == EASY) { g.setColor(Color.orange); }
      if (cost == BLOCK) { g.setColor(Color.black); }
      if (cost == TOUGH) { g.setColor(Color.lightGray); }
      if (cost == VERY_TOUGH) { g.setColor(Color.gray); }
    }
    if (showPath && partOfPath) { g.setColor(Color.yellow); }
    if (startCell == this) { g.setColor(Color.green); }
    if (finishCell == this) { g.setColor(Color.red); }
    g.fillRect(0, 0, size.width, size.height); // draw field

    if (showDist && distFromStart > 0) {
      g.setColor(Color.black);
      g.setFont(new Font("Courier", Font.PLAIN, 10));
      g.drawString("" + distFromStart, 1, size.height - 3);
    }
    g.setColor(new Color(222, 222, 222));
    g.drawRect(0, 0, size.width, size.height); // draw grid
  }

  /**
   * reads an object from an object stream
   * @param ois stream
   */
  private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    if (!tidy) {
      cells = new Vector();
      tidy = true;
    }
    ois.defaultReadObject();
    cells.addElement(this);
    if (isStart) { setStart(true); }
    if (isFinish) { setFinish(true); }
  }
}